home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / pyshared / DistUpgrade / DistUpgradeViewNonInteractive.py < prev    next >
Encoding:
Python Source  |  2009-04-27  |  12.5 KB  |  322 lines

  1. # DistUpgradeView.py 
  2. #  
  3. #  Copyright (c) 2004,2005 Canonical
  4. #  
  5. #  Author: Michael Vogt <michael.vogt@ubuntu.com>
  6. #  This program is free software; you can redistribute it and/or 
  7. #  modify it under the terms of the GNU General Public License as 
  8. #  published by the Free Software Foundation; either version 2 of the
  9. #  License, or (at your option) any later version.
  10. #  This program is distributed in the hope that it will be useful,
  11. #  but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. #  GNU General Public License for more details.
  14. #  You should have received a copy of the GNU General Public License
  15. #  along with this program; if not, write to the Free Software
  16. #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  17. #  USA
  18.  
  19. import apt
  20. import logging
  21. import time
  22. import sys
  23. from DistUpgradeView import DistUpgradeView, InstallProgress, FetchProgress
  24. from DistUpgradeConfigParser import DistUpgradeConfig
  25. import os
  26. import pty
  27. import apt_pkg
  28. import select
  29. import fcntl
  30. import string
  31. import re
  32. import subprocess
  33. from subprocess import call, PIPE, Popen
  34. import copy
  35. import apt.progress
  36. from ConfigParser import NoSectionError, NoOptionError
  37.  
  38. class NonInteractiveFetchProgress(FetchProgress):
  39.     def updateStatus(self, uri, descr, shortDescr, status):
  40.         FetchProgress.updateStatus(self, uri, descr, shortDescr, status)
  41.         #logging.debug("Fetch: updateStatus %s %s" % (uri, status))
  42.         if status == apt.progress.FetchProgress.dlDone:
  43.             print "fetched %s (%.2f/100) at %sb/s" % (uri, self.percent, 
  44.                                                       apt_pkg.SizeToStr(int(self.currentCPS)))
  45.             if sys.stdout.isatty():
  46.                 sys.stdout.flush()
  47.         
  48.  
  49. class NonInteractiveInstallProgress(InstallProgress):
  50.     def __init__(self):
  51.         InstallProgress.__init__(self)
  52.         logging.debug("setting up environ for non-interactive use")
  53.         os.environ["DEBIAN_FRONTEND"] = "noninteractive"
  54.         os.environ["APT_LISTCHANGES_FRONTEND"] = "none"
  55.         os.environ["RELEASE_UPRADER_NO_APPORT"] = "1"
  56.         self.config = DistUpgradeConfig(".")
  57.         try:
  58.             if self.config.getboolean("NonInteractive","ForceOverwrite"):
  59.                 apt_pkg.Config.Set("DPkg::Options::","--force-overwrite")
  60.         except (NoSectionError, NoOptionError), e:
  61.             pass
  62.         # more debug
  63.         #apt_pkg.Config.Set("Debug::pkgOrderList","true")
  64.         #apt_pkg.Config.Set("Debug::pkgDPkgPM","true")
  65.         # default to 2400 sec timeout
  66.         self.timeout = 2400
  67.         try:
  68.             self.timeout = self.config.getint("NonInteractive","TerminalTimeout")
  69.         except Exception, e:
  70.             pass
  71.     
  72.     def error(self, pkg, errormsg):
  73.         # re-run maintainer script with sh -x/perl debug to get a better 
  74.         # idea what went wrong
  75.         # 
  76.         # FIXME: this is just a approximation for now, we also need
  77.         #        to pass:
  78.         #        - a version after remove (if upgrade to new version)
  79.         #
  80.         #        not everything is a shell or perl script
  81.         #
  82.         # if the new preinst fails, its not yet in /var/lib/dpkg/info
  83.         # so this is inaccurate as well
  84.         logging.error("got a error from dpkg for pkg: '%s': '%s'" % (pkg, errormsg))
  85.         environ = copy.copy(os.environ)
  86.         environ["PYCENTRAL"] = "debug"
  87.         cmd = []
  88.  
  89.         # find what maintainer script failed
  90.         if "post-installation" in errormsg:
  91.             prefix = "/var/lib/dpkg/info/"
  92.             name = "postinst"
  93.             argument = "configure"
  94.             maintainer_script = "%s/%s.%s" % (prefix, pkg, name)
  95.         elif "pre-installation" in errormsg:
  96.             prefix = "/var/lib/dpkg/tmp.ci/"
  97.             #prefix = "/var/lib/dpkg/info/"
  98.             name = "preinst"
  99.             argument = "install"
  100.             maintainer_script = "%s/%s" % (prefix, name)
  101.         elif "pre-removal" in errormsg:
  102.             prefix = "/var/lib/dpkg/info/"
  103.             name = "prerm"
  104.             argument = "remove"
  105.             maintainer_script = "%s/%s.%s" % (prefix, pkg, name)
  106.         elif "post-removal" in errormsg:
  107.             prefix = "/var/lib/dpkg/info/"
  108.             name = "postrm"
  109.             argument = "remove"
  110.             maintainer_script = "%s/%s.%s" % (prefix, pkg, name)
  111.         else:
  112.             print "UNKNOWN (trigger?) dpkg/script failure for %s (%s) " % (pkg, errormsg)
  113.             return
  114.  
  115.         # find out about the interpreter
  116.         if not os.path.exists(maintainer_script):
  117.             logging.error("can not find failed maintainer script '%s' " % maintainer_script)
  118.             return
  119.         interp = open(maintainer_script).readline()[2:].strip().split()[0]
  120.         if ("bash" in interp) or ("/bin/sh" in interp):
  121.             debug_opts = ["-ex"]
  122.         elif ("perl" in interp):
  123.             debug_opts = ["-d"]
  124.             environ["PERLDB_OPTS"] = "AutoTrace NonStop"
  125.         else:
  126.             logging.warning("unknown interpreter: '%s'" % interp)
  127.  
  128.         # check if debconf is used and fiddle a bit more if it is
  129.         if ". /usr/share/debconf/confmodule" in open(maintainer_script).read():
  130.             environ["DEBCONF_DEBUG"] = "developer"
  131.             environ["DEBIAN_HAS_FRONTEND"] = "1"
  132.             interp = "/usr/share/debconf/frontend"
  133.             debug_opts = ["sh","-ex"]
  134.  
  135.         # build command
  136.         cmd.append(interp)
  137.         cmd.extend(debug_opts)
  138.         cmd.append(maintainer_script)
  139.         cmd.append(argument)
  140.  
  141.         # check if we need to pass a version
  142.         if name == "postinst":
  143.             version = Popen("dpkg-query -s %s|grep ^Config-Version" % pkg,shell=True, stdout=PIPE).communicate()[0]
  144.             if version:
  145.                 cmd.append(version.split(":",1)[1].strip())
  146.         elif name == "preinst":
  147.             pkg = os.path.basename(pkg)
  148.             pkg = pkg.split("_")[0]
  149.             version = Popen("dpkg-query -s %s|grep ^Version" % pkg,shell=True, stdout=PIPE).communicate()[0]
  150.             if version:
  151.                 cmd.append(version.split(":",1)[1].strip())
  152.  
  153.         print cmd
  154.         logging.debug("re-running '%s' (%s)" % (cmd, environ))
  155.         ret = subprocess.call(cmd, env=environ)
  156.         logging.debug("%s script returned: %s" % (name,ret))
  157.         
  158.     def conffile(self, current, new):
  159.         logging.warning("got a conffile-prompt from dpkg for file: '%s'" % current)
  160.         # looks like we have a race here *sometimes*
  161.         time.sleep(5)
  162.     try:
  163.           # don't overwrite
  164.       os.write(self.master_fd,"n\n")
  165.      except Exception, e:
  166.       logging.error("error '%s' when trying to write to the conffile"%e)
  167.  
  168.     def startUpdate(self):
  169.         InstallProgress.startUpdate(self)
  170.         self.last_activity = time.time()
  171.           
  172.     def updateInterface(self):
  173.         if self.statusfd == None:
  174.             return
  175.  
  176.         if (self.last_activity + self.timeout) < time.time():
  177.             logging.warning("no activity %s seconds (%s) - sending ctrl-c" % (self.timeout, self.status))
  178.             # ctrl-c
  179.             os.write(self.master_fd,chr(3))
  180.  
  181.  
  182.         # read status fd from dpkg
  183.         # from python-apt/apt/progress.py (but modified a bit)
  184.         # -------------------------------------------------------------
  185.         res = select.select([self.statusfd],[],[],0.1)
  186.         while len(res[0]) > 0:
  187.             self.last_activity = time.time()
  188.             while not self.read.endswith("\n"):
  189.                 self.read += os.read(self.statusfd.fileno(),1)
  190.             if self.read.endswith("\n"):
  191.                 s = self.read
  192.                 #print s
  193.                 (status, pkg, percent, status_str) = string.split(s, ":")
  194.                 if status == "pmerror":
  195.                     self.error(pkg,status_str)
  196.                 elif status == "pmconffile":
  197.                     # we get a string like this:
  198.                     # 'current-conffile' 'new-conffile' useredited distedited
  199.                     match = re.compile("\s*\'(.*)\'\s*\'(.*)\'.*").match(status_str)
  200.                     if match:
  201.                         self.conffile(match.group(1), match.group(2))
  202.                 elif status == "pmstatus":
  203.                     if (float(percent) != self.percent or 
  204.                         status_str != self.status):
  205.                         self.statusChange(pkg, float(percent), status_str.strip())
  206.                         self.percent = float(percent)
  207.                         self.status = string.strip(status_str)
  208.                         sys.stdout.write("[%s] %s: %s\n" % (float(percent), pkg, status_str.strip()))
  209.                         sys.stdout.flush()
  210.             self.read = ""
  211.             res = select.select([self.statusfd],[],[],0.1)
  212.         # -------------------------------------------------------------
  213.  
  214.         #fcntl.fcntl(self.master_fd, fcntl.F_SETFL, os.O_NDELAY)
  215.         # read master fd (terminal output)
  216.         res = select.select([self.master_fd],[],[],0.1)
  217.         while len(res[0]) > 0:
  218.            self.last_activity = time.time()
  219.            try:
  220.                s = os.read(self.master_fd, 1)
  221.                sys.stdout.write("%s" % s)
  222.            except OSError,e:
  223.                # happens after we are finished because the fd is closed
  224.                return
  225.            res = select.select([self.master_fd],[],[],0.1)
  226.         sys.stdout.flush()
  227.  
  228.     def fork(self):
  229.         logging.debug("doing a pty.fork()")
  230.         # some maintainer scripts fail without
  231.         os.environ["TERM"] = "dumb"
  232.         (self.pid, self.master_fd) = pty.fork()
  233.         if self.pid != 0:
  234.             logging.debug("pid is: %s" % self.pid)
  235.         return self.pid
  236.         
  237.  
  238. class DistUpgradeViewNonInteractive(DistUpgradeView):
  239.     " non-interactive version of the upgrade view "
  240.     def __init__(self, datadir=None, logdir=None):
  241.         self.config = DistUpgradeConfig(".")
  242.         self._fetchProgress = NonInteractiveFetchProgress()
  243.         self._installProgress = NonInteractiveInstallProgress()
  244.         self._opProgress = apt.progress.OpProgress()
  245.         sys.__excepthook__ = self.excepthook
  246.     def excepthook(self, type, value, traceback):
  247.         " on uncaught exceptions -> print error and reboot "
  248.         logging.error("got exception '%s': %s " % (type, value))
  249.         #sys.excepthook(type, value, traceback)
  250.         self.confirmRestart()
  251.     def getOpCacheProgress(self):
  252.         " return a OpProgress() subclass for the given graphic"
  253.         return self._opProgress
  254.     def getFetchProgress(self):
  255.         " return a fetch progress object "
  256.         return self._fetchProgress
  257.     def getInstallProgress(self, cache=None):
  258.         " return a install progress object "
  259.         return self._installProgress
  260.     def updateStatus(self, msg):
  261.         """ update the current status of the distUpgrade based
  262.             on the current view
  263.         """
  264.         pass
  265.     def setStep(self, step):
  266.         """ we have 5 steps current for a upgrade:
  267.         1. Analyzing the system
  268.         2. Updating repository information
  269.         3. Performing the upgrade
  270.         4. Post upgrade stuff
  271.         5. Complete
  272.         """
  273.         pass
  274.     def confirmChanges(self, summary, changes, downloadSize,
  275.                        actions=None, removal_bold=True):
  276.         DistUpgradeView.confirmChanges(self, summary, changes, downloadSize, actions)
  277.     logging.debug("toinstall: '%s'" % self.toInstall)
  278.         logging.debug("toupgrade: '%s'" % self.toUpgrade)
  279.         logging.debug("toremove: '%s'" % self.toRemove)
  280.         return True
  281.     def askYesNoQuestion(self, summary, msg):
  282.         " ask a Yes/No question and return True on 'Yes' "
  283.         return True
  284.     def confirmRestart(self):
  285.         " generic ask about the restart, can be overridden "
  286.     logging.debug("confirmRestart() called")
  287.         # ignore if we don't have this option
  288.         try:
  289.             # rebooting here makes sense if we run e.g. in qemu
  290.             return self.config.getboolean("NonInteractive","RealReboot")
  291.         except Exception, e:
  292.             logging.debug("no RealReboot found, returning false (%s) " % e)
  293.             return False
  294.     def error(self, summary, msg, extended_msg=None):
  295.         " display a error "
  296.         logging.error("%s %s (%s)" % (summary, msg, extended_msg))
  297.     def abort(self):
  298.         logging.error("view.abort called")
  299.  
  300.  
  301. if __name__ == "__main__":
  302.  
  303.   view = DistUpgradeViewNonInteractive()
  304.   fp = NonInteractiveFetchProgress()
  305.   ip = NonInteractiveInstallProgress()
  306.  
  307.   #ip.error("linux-image-2.6.17-10-generic","post-installation script failed")
  308.   ip.error("xserver-xorg","pre-installation script failed")
  309.  
  310.   cache = apt.Cache()
  311.   for pkg in sys.argv[1:]:
  312.     #if cache[pkg].isInstalled:
  313.     #  cache[pkg].markDelete()
  314.     #else:
  315.     cache[pkg].markInstall()
  316.   cache.commit(fp,ip)
  317.   time.sleep(2)
  318.   sys.exit(0)
  319.